/*
 *  Shader.cpp
 *  CS248-Final-Project
 *
 *  Created by Matthew Fichman on 1/25/11.
 *  Copyright 2011 __MyCompanyName__. All rights reserved.
 *
 */

#include "Shader.hpp"
#include <fstream>

#define ERROR_BUFSIZE 1024

Shader::Shader(const std::string& path) :
  path_(path),
  vertexShaderID_(0),
  fragmentShaderID_(0),
  geoShaderID_(0),
  tessEvalShaderID_(0),
  tessCtrlShaderID_(0),
  programID_(0),
  loaded_(false),
  geoShader(false),
  tessShader(false){

  // Create the vertex program
  programID_ = glCreateProgram();

  const GLchar* source[1];
  int length = 0;
	
  // Load the fragment shader and compile
  std::vector<char> fragmentSource = readSource(path + ".frag.glsl");
  source[0] = &fragmentSource.front();
  length = fragmentSource.size()-1;
  fragmentShaderID_ = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShaderID_, 1, source, &length);
  glCompileShader(fragmentShaderID_);
		
  // Load the vertex shader and compile
  std::vector<char> vertexSource = readSource(path + ".vert.glsl");
  source[0] = &vertexSource.front();
  length = vertexSource.size()-1;
  vertexShaderID_ = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShaderID_, 1, source, &length);
  glCompileShader(vertexShaderID_);
	

  // Load the geo shader and compile
  std::vector<char> geoSource = readSource(path + ".geo.glsl");
  if(geoSource[0]!=0){
    cout<<path<<" has geometry shader\n";
    geoShader = true;
    source[0] = &geoSource.front();
    length = geoSource.size()-1;
    geoShaderID_ = glCreateShader(GL_GEOMETRY_SHADER_EXT);
    glShaderSource(geoShaderID_, 1, source, &length);
    glCompileShader(geoShaderID_);
    

    GLint result = 0;
    glGetShaderiv(geoShaderID_, GL_COMPILE_STATUS, &result);
    if(result != GL_TRUE) {
      GLsizei length = 0;
   // Buffer for error messages
    static const int kBufferSize = 1024;
    char buffer[1024];
    glGetShaderInfoLog(geoShaderID_, kBufferSize-1,
		       &length, buffer);
    fprintf(stderr, "geo shader: GLSL error\n%s\n", buffer);
    }

  }

  // Load the tess shader and compile
  std::vector<char> tessSource = readSource(path + ".tesse.glsl");
  if(tessSource[0]!=0){
    //Evaluation
    cout<<path<<" has tessellation eval shader\n";
    source[0] = &tessSource.front();
    length = tessSource.size()-1;
    tessEvalShaderID_ = glCreateShader(GL_TESS_EVALUATION_SHADER);
    glShaderSource(tessEvalShaderID_, 1, source, &length);
    glCompileShader(tessEvalShaderID_);
   
    GLint result = 0;
    glGetShaderiv(tessEvalShaderID_, GL_COMPILE_STATUS, &result);
    if(1){//if(result != GL_TRUE) {
      GLsizei length = 0;
      // Buffer for error messages
      static const int kBufferSize = 1024;
      char buffer[1024];
      glGetShaderInfoLog(tessEvalShaderID_, kBufferSize-1,
			 &length, buffer);
      fprintf(stderr, "eval shader:  GLSL message\n%s\n", buffer);
    }

    //Control
    tessSource.clear();
    tessSource = readSource(path + ".tessc.glsl");
    if(tessSource[0]!=0){
      cout<<path<<" has tessellation control shader\n";
      source[0] = &tessSource.front();
      length = tessSource.size()-1;
      tessCtrlShaderID_ = glCreateShader(GL_TESS_CONTROL_SHADER);
      glShaderSource(tessCtrlShaderID_, 1, source, &length);
      glCompileShader(tessCtrlShaderID_);
   
      GLint result = 0;
      glGetShaderiv(tessCtrlShaderID_, GL_COMPILE_STATUS, &result);
      if(1){//if(result != GL_TRUE) {
	GLsizei length = 0;
	// Buffer for error messages
	static const int kBufferSize = 1024;
	char buffer[1024];
	glGetShaderInfoLog(tessCtrlShaderID_, kBufferSize-1,
			   &length, buffer);
	fprintf(stderr, "control shader: GLSL message\n%s\n", buffer);
      }
    tessShader = true;
    }
  }



  glAttachShader(programID_, fragmentShaderID_);
  glAttachShader(programID_, vertexShaderID_);
  if(geoShader){
    glAttachShader(programID_, geoShaderID_);
    //geometry shader handles only triangles!
    
    int temp;
    glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT,&temp);
    glProgramParameteriEXT(programID_,GL_GEOMETRY_VERTICES_OUT_EXT,temp);
    glProgramParameteriEXT(programID_,GL_GEOMETRY_INPUT_TYPE_EXT ,GL_TRIANGLES);
    glProgramParameteriEXT(programID_,GL_GEOMETRY_OUTPUT_TYPE_EXT ,GL_TRIANGLE_STRIP);
    
  }
  if(tessShader){
    glAttachShader(programID_, tessEvalShaderID_); 
    glAttachShader(programID_, tessCtrlShaderID_); 
  }
  glLinkProgram(programID_);
	
  // Error checking
  glGetProgramiv(programID_, GL_LINK_STATUS, (GLint*)&loaded_);
  //glGetShaderiv(vertexShaderID_, GL_COMPILE_STATUS, (GLint*)&loaded_);
  if (!loaded_) {
    GLchar tempErrorLog[ERROR_BUFSIZE];
    GLsizei length;
    glGetShaderInfoLog(fragmentShaderID_, ERROR_BUFSIZE, &length, tempErrorLog);
    errors_ += "Fragment shader errors:\n";
    errors_ += std::string(tempErrorLog, length) + "\n";
    glGetShaderInfoLog(vertexShaderID_, ERROR_BUFSIZE, &length, tempErrorLog);
    errors_ += "Vertex shader errors:\n";
    errors_ += std::string(tempErrorLog, length) + "\n";
    if(geoShader){
      glGetShaderInfoLog(geoShaderID_, ERROR_BUFSIZE, &length, tempErrorLog);
      errors_ += "Geo shader errors:\n";
      errors_ += std::string(tempErrorLog, length) + "\n";
    }
    if(tessShader){
      glGetShaderInfoLog(tessEvalShaderID_, ERROR_BUFSIZE, &length, tempErrorLog);
      errors_ += "Tess Eval shader errors:\n";
      errors_ += std::string(tempErrorLog, length) + "\n";
      glGetShaderInfoLog(tessCtrlShaderID_, ERROR_BUFSIZE, &length, tempErrorLog);
      errors_ += "Tess control shader errors:\n";
      errors_ += std::string(tempErrorLog, length) + "\n";
    }
    glGetProgramInfoLog(programID_, ERROR_BUFSIZE, &length, tempErrorLog);
    errors_ += "Linker errors:\n";
    errors_ += std::string(tempErrorLog, length) + "\n";
  }

}

Shader::~Shader() {
  glDeleteShader(vertexShaderID_);
  glDeleteShader(fragmentShaderID_);
  if(geoShader)
    glDeleteShader(geoShaderID_);
  if(tessShader){
    glDeleteShader(tessEvalShaderID_);
    glDeleteShader(tessCtrlShaderID_);
  }
  glDeleteProgram(programID_);
}

std::vector<char> Shader::readSource(const std::string& path) {

  // Open the file
  std::vector<char> source;
  std::ifstream in(path.c_str());
  if (in.fail()) {
    source.push_back(0);
    return source;
  }
	
  // Seek to the end of the file to get the size
  in.seekg(0, std::ios::end);
  source.reserve((unsigned)(1 + in.tellg()));
  source.resize((unsigned)in.tellg());
  in.seekg(0, std::ios::beg);
  if (source.empty()) {
    source.push_back(0);
    return source;
  }
	
  // Now read the whole buffer in one call, and don't 
  // forget to null-terminate the vector with a zero
  in.read(&source.front(), source.size());
  source.push_back(0);
	
  return source;
}

const std::string& Shader::path() const {
  return path_;
}

GLuint Shader::programID() const {
  return programID_;
}

const std::string& Shader::errors() const {
  return errors_;
}

bool Shader::loaded() const {
  return loaded_;
}

void Shader::BindAttributeLocation(string s,GLuint index){
  if(attributeLocation.find(s)==attributeLocation.end()){
    attributeLocation[s] = index;
  }
  glBindAttribLocation(programID(),index, s.c_str());
}

GLint Shader::GetAttributeLocation(string s){
  if(attributeLocation.find(s)==attributeLocation.end()){
    attributeLocation[s] = glGetAttribLocation(programID(), s.c_str());
  }
  return attributeLocation[s];
}

GLint Shader::GetLocation(string s){
  if(uniformLocation.find(s)==uniformLocation.end()){
    uniformLocation[s] = glGetUniformLocation(programID(),s.c_str());
  }
  return uniformLocation[s];
}
